/**
 * 
 */
package com.dfhc.util.aes;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;

import com.dfhc.ISystemConstant;
import com.dfhc.util.ConvertHelper;
import com.dfhc.util.StringHelper;

/**
 * @author 龙色波
 * OpenSSL compatible AES CBC encryption
 */
public class GibberishAES {
	private int size=0;
	private int Nr;
	private int Nk;
	private boolean Decrypt;
	private int SBox[] = null;
	private int Rcon[] = null;
	private int[] SBoxInv = null;
	private int decryptShiftBy[]={0, 13, 10, 7, 4, 1, 14, 11, 8, 5, 2, 15, 12, 9, 6, 3};
	private int ecryptShiftBy[]={0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12, 1, 6, 11};
	private int[] G2X;
	private int[] G3X;
	private int[] G9X;
	private int[] GBX;
	private int[] GDX;
	private int[] GEX;
	private MD5 md5;
	private Base64 base64;
	public GibberishAES(){
		SBox = strhex("637c777bf26b6fc53001672bfed7ab76ca82c97dfa5947f0add4a2af9ca472c0b7fd9326363ff7cc34a5e5f171d8311504c723c31896059a071280e2eb27b27509832c1a1b6e5aa0523bd6b329e32f8453d100ed20fcb15b6acbbe394a4c58cfd0efaafb434d338545f9027f503c9fa851a3408f929d38f5bcb6da2110fff3d2cd0c13ec5f974417c4a77e3d645d197360814fdc222a908846eeb814de5e0bdbe0323a0a4906245cc2d3ac629195e479e7c8376d8dd54ea96c56f4ea657aae08ba78252e1ca6b4c6e8dd741f4bbd8b8a703eb5664803f60e613557b986c11d9ee1f8981169d98e949b1e87e9ce5528df8ca1890dbfe6426841992d0fb054bb16",2);
		Rcon = strhex("01020408102040801b366cd8ab4d9a2f5ebc63c697356ad4b37dfaefc591",2);
		SBoxInv = invertArr(SBox);
		G2X = Gx((int)2);
		G3X = Gx((int)3);
		G9X = Gx((int)9);
		GBX = Gx((int)0xb);
		GDX = Gx((int)0xd);
		GEX = Gx((int)0xe);
		md5 = new MD5();
		base64 = new Base64();
	}
	/**
	 * 编码为UTF-8
	 * @param value
	 * @return
	 * @throws UnsupportedEncodingException
	 */
	public synchronized String enc_utf8(String value) throws UnsupportedEncodingException{
		//URLEncoder.encode 有个问题：将空格转义为+,应该 %20		
		String encode = URLEncoder.encode(value,ISystemConstant.ENCODE_UTF_8);
		encode = StringHelper.replaceAll(encode,"+","%20").toString();
		return JsEncode.unescape(encode);
	}
	/**
	 * 解码为UTF-8
	 * @param value
	 * @return
	 * @throws UnsupportedEncodingException
	 */
	public synchronized String dec_utf8 (String value) throws UnsupportedEncodingException{
		return URLDecoder.decode(JsEncode.escape(value), ISystemConstant.ENCODE_UTF_8);
	}
	
	
	/**
	 * 块转换为字符串
	 * @param block 字节数组
	 * @param lastBlock 是否为最后一块
	 * @return
	 * @throws Exception 
	 */
	private String block2s (int  block[], boolean lastBlock) throws Exception{
        String string = "";
        int	 padding, i;
        if (lastBlock) {
            padding = block[15];
            if (padding > 16) {
                throw new Exception("Decryption error: Maybe bad key");
            }
            if (padding == 16) {
                return "";
            }
            for (i = 0; i < 16 - padding; i++) {
//                string += ConvertHelper.toString(block[i]);
            	  string += StringHelper.fromCharCode(block[i]);
            }
//            string = StringHelper.fromCharCode(block);
        } else {
//            for (i = 0; i < 16; i++) {
//                string += ConvertHelper.toString(block[i]);
//            }
        	string = StringHelper.fromCharCode(block);
        }
        return string;
	}	
	/**
	 * 字符串转ascii
	 * @param string
	 * @param binary 是否为二进制
	 * @return
	 * @throws UnsupportedEncodingException
	 */
	private int [] s2a(String string, boolean binary) throws UnsupportedEncodingException {
        int[] array = null;
        int i;

        if (! binary) {
            string = enc_utf8(string);
        }
        array = new int[string.length()];
        for (i = 0; i < string.length(); i++)
        {
            array[i] = StringHelper.getUnicodeInt(string.charAt(i));
        }

        return array;
    }
	public synchronized int getSize() {
		return size;
	}
	/**
	 * 设置位数
	 * @param size
	 * @throws Exception 
	 */
	public synchronized void setSize(int size) throws Exception {
		 switch (size){
	        case 128:
	            Nr = 10;
	            Nk = 4;
	            break;
	        case 192:
	            Nr = 12;
	            Nk = 6;
	            break;
	        case 256:
	            Nr = 14;
	            Nk = 8;
	            break;
	        default:
	            throw new Exception("Invalid Key Size Specified:" + size);
	        }
		this.size = size;
	}
	/**
	 * 产生指定位数的随机串
	 * @param num
	 * @return
	 */
	private int[] randArr (int num) {
        int result[] =null, i;
        
        result = new int[num];
        for (i = 0; i < num; i++) {
            result[i] = (int) Math.floor(Math.random() * 256);
        }
        return result;
    }
	/**
	 * 产生ssl key
	 * @param passwordArr
	 * @param saltArr
	 * @return
	 * @throws Exception 
	 */
	private Map<String, int[]> openSSLKey (int passwordArr[], int saltArr[]) throws Exception {
	        // Number of rounds depends on the size of the AES in use
	        // 3 rounds for 256
	        //        2 rounds for the key, 1 for the IV
	        // 2 rounds for 128
	        //        1 round for the key, 1 round for the IV
	        // 3 rounds for 192 since it's not evenly divided by 128 bits
	        int rounds = Nr >= 12 ? 3: 2;
	        int key[];
	        int iv[];
	        int md5_hash[][] = null;
	        int result[];
	        int data00[];
	        
	        data00 = ConvertHelper.addInts(passwordArr,saltArr);
	        
	        md5_hash = new int[rounds][];
	        int i;
	        md5_hash[0] = md5.encode(data00);
	        result = md5_hash[0];
	        for (i = 1; i < rounds; i++) {
	            md5_hash[i] = md5.encode(ConvertHelper.addInts(md5_hash[i - 1],data00));
	            result = ConvertHelper.addInts(result,md5_hash[i]);
	        }
	        key = new int[4*Nk];
	        System.arraycopy(result, 0, key, 0, 4*Nk);// result.slice(0, 4 * Nk);
	        //iv = result.slice(4 * Nk, 4 * Nk + 16);
	        iv = new int[4*Nk+16-4*Nk];
	        System.arraycopy(result, 4*Nk, iv, 0, iv.length);
	        Map<String,int[]> map = new HashMap<String,int[]>();
	        map.put("key", key);
	        map.put("iv", iv);
	        return map;
//	        return {
//	            key: key,
//	            iv: iv
//	        };
	    }	
	public synchronized String enc(String string, String pass,boolean binary) throws UnsupportedEncodingException, Exception {
        // string, password in plaintext
        //检查密钥长度
    	checkSize();
		int[] salt = randArr(8);
        Map<String, int[]> pbe = openSSLKey(s2a(pass, binary), salt);
        int[] key = pbe.get("key");
        int[] iv = pbe.get("iv");
        int cipherBlocks[][] = null;
        int saltBlock[][] = {{83, 97, 108, 116, 101, 100, 95, 95}};
        saltBlock[0] = ConvertHelper.addInts(saltBlock[0],salt);
        int[] string1 = s2a(string, binary);
        cipherBlocks = rawEncrypt(string1, key, iv);
        // Spells out 'Salted__'
        int swapBlocks[][] = null;
        swapBlocks = new int[cipherBlocks.length+1][];
        swapBlocks[0] = saltBlock[0];
        for(int i=0;i<cipherBlocks.length;i++){
        	swapBlocks[i+1] = cipherBlocks[i];
        }
        
        
        return base64.encode(swapBlocks,false);
    }

    public synchronized String  dec (String string, String pass,boolean binary) throws Exception {
        // string, password in plaintext    	
        //检查密钥长度
    	checkSize();
    	int cryptArr[] = base64.decode(string);
        int salt[] = new int[8];
        for(int i=8;i<16;i++){
        	salt[i-8]=cryptArr[i];
        }
        
        Map<String, int[]> pbe = openSSLKey(s2a(pass, binary), salt);
        int[] key = pbe.get("key");
        int[] iv = pbe.get("iv");
        cryptArr = ConvertHelper.subInts(cryptArr, 16, cryptArr.length-16);
        // Take off the Salted__ffeeddcc
        string = rawDecrypt(cryptArr, key, iv, binary);
        return string;
    }	
    private int[][] rawEncrypt (int[] string1,int key[], int iv[]) {
        // plaintext, key and iv as int arrays
        int[][] key1 = expandKey(key);
        double d1 = string1.length;
        double d2 = 16;
        double d = d1 / d2;
        int numBlocks = ConvertHelper.toInt(Math.ceil(d));
        int blocks  [][] =null;
        int i;
        int cipherBlocks [][]=null;
        //如果块个数为0，则设置为1，防止下标越界
        
        if(string1.length>=16){
        	blocks = new int[(numBlocks==0?1:numBlocks)+(string1.length % 16==0?1:0)][];
        }else{
        	blocks = new int[numBlocks==0?1:numBlocks][];
        }
        cipherBlocks = new int[blocks.length][];
        for (i = 0; i < (numBlocks==0?1:numBlocks); i++) {
        	int len = (i*16+16>string1.length)?(string1.length-i*16):16;
            blocks[i] = padBlock(ConvertHelper.subInts(string1,i * 16, len));
        }
        if (string1.length % 16 == 0) {
            //blocks.push([16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16]);
        	int tmp[] = new int[16];
        	for(i=0;i<16;i++){
        		tmp[i]=16;
        	}
        	blocks[numBlocks] = tmp;
            // CBC OpenSSL padding scheme
            numBlocks++;
        }
        for (i = 0; i < blocks.length; i++) {
            blocks[i] = (i == 0) ? xorBlocks(blocks[i], iv) : xorBlocks(blocks[i], cipherBlocks[i - 1]);
            cipherBlocks[i] = encryptBlock(blocks[i], key1);
        }
        return cipherBlocks;
    }

    private String rawDecrypt (int cryptArr[], int key[], int iv[],boolean binary) throws Exception {
        //  cryptArr, key and iv as int arrays
        int[][] key1 = expandKey(key);
        int numBlocks = cryptArr.length / 16;
        int cipherBlocks [][]=null;
        int i;
        int plainBlocks[][] = new int[numBlocks][];
        
        String string = "";
        
        cipherBlocks = new int[numBlocks][];
        
        for (i = 0; i < numBlocks; i++) {
            //cipherBlocks[i]=(cryptArr.slice(i * 16, (i + 1) * 16));
        	cipherBlocks[i]= ConvertHelper.subInts(cryptArr, i*16, (i + 1) * 16 -i*16);
        }
        for (i = cipherBlocks.length - 1; i >= 0; i--) {
            plainBlocks[i] = decryptBlock(cipherBlocks[i], key1);
            plainBlocks[i] = (i == 0) ? xorBlocks(plainBlocks[i], iv) : xorBlocks(plainBlocks[i], cipherBlocks[i - 1]);
        }
        for (i = 0; i < numBlocks - 1; i++) {
            string += block2s(plainBlocks[i], false);
        }
        string += block2s(plainBlocks[i], true);
        return binary ? string : dec_utf8(string); 
    }
    private int [][] expandKey (int key[]) {
        // Expects a 1d number array
        int w[][];
        int temp [];
        int i;
        int r[];
        int t;
        int flat[][];
        int j;
        
        flat = new int[Nr + 1][];
        temp = new int[4];
        
        w = new int[4 * (Nr + 1)][];
        
        for (i = 0; i < Nk; i++) {
        	r = new int[4];
        	r[0] = key[4 * i];
        	r[1] = key[4 * i + 1];
        	r[2] = key[4 * i + 2];
        	r[3] = key[4 * i + 3];            
            w[i] = r;
        }

        for (i = Nk; i < (4 * (Nr + 1)); i++) {
            w[i] = new int[4];
            for (t = 0; t < 4; t++) {
                temp[t] = w[i - 1][t];
            }
            if (i % Nk == 0) {
                temp = subWord(rotWord(temp));
                temp[0] ^= Rcon[i / Nk - 1];
            } else if (Nk > 6 && i % Nk == 4) {
                temp = subWord(temp);
            }
            for (t = 0; t < 4; t++) {
                w[i][t] = (int) (w[i - Nk][t] ^ temp[t]);
            }
        }
        for (i = 0; i < (Nr + 1); i++) {
            flat[i] = new int[16];
            int k=0;
            for (j = 0; j < 4; j++) {            	
            	flat[i][k++]=w[i * 4 + j][0];
            	flat[i][k++]=w[i * 4 + j][1];
            	flat[i][k++]=w[i * 4 + j][2];
            	flat[i][k++]=w[i * 4 + j][3];                
            }
        }
        return flat;
    }
    private int[] padBlock (int byteArr[]){
    	        int array[] = null;
    	        int cpad;
    	        int i;
    	        
    	        if(byteArr.length<16){
    	        	array = new int[16];
    	        }else{
    	        	array = new int[byteArr.length];
    	        }
    	        
    	        if (byteArr.length < 16) {
    	            cpad = (int)(16 - byteArr.length);
    	            
    	            for(i=0;i<16;i++){
    	            	array[i] = cpad;
    	            }    	            
    	        }
    	        
    	        for (i = 0; i < byteArr.length; i++)
    	        {
    	            array[i] = byteArr[i];
    	        }
    	        return array;
    	    }
    /**
     * 块xor
     * @param block1
     * @param block2
     * @return
     */
    private int[] xorBlocks (int block1[], int block2[]) {
        int temp [] = new int[16];
        int i;
        for (i = 0; i < 16; i++) {
            temp[i] = (int) (block1[i] ^ block2[i]);
        }
        return temp;
    }
    /**
     * 加密块
     * @param block
     * @param words
     * @return
     */
    private int[] encryptBlock (int block[], int [][]words) {
        Decrypt = false;
        int state[] = addRoundKey(block, words, 0),
        round;
        for (round = 1; round < (Nr + 1); round++) {
            state = subBytes(state);
            state = shiftRows(state);
            if (round < Nr) {
                state = mixColumns(state);
            }
            //last round? don't mixColumns
            state = addRoundKey(state, words, round);
        }

        return state;
    }    
    /**
     * 
     * @param state
     * @param words
     * @param round
     * @return
     */
    private int [] addRoundKey (int state[],int words[][], int round) {
        int temp[]= new int[16];
        int i;
        for (i = 0; i < 16; i++) {
            temp[i] = (int) (state[i] ^ words[round][i]);
        }
        return temp;
    }
    
    private int[] rotWord (int w[]) {
        // rotate 4-int word w left by one int
        int tmp = w[0];
        int i;
        for (i = 0; i < 3; i++) {
            w[i] = w[i + 1];
        }
        w[3] = tmp;
        return w;
    }
   private  int[] subWord (int w[]) {
        // apply SBox to 4-int word w
        for (int i = 0; i < 4; i++) {
            w[i] = SBox[w[i]];
        }
        return w;
    }
   /**
    * 字符串转十六进制
    * @param str
    * @param size
    * @return
    */
   private int[] strhex (String str,int size) {
       int i;
       int ret[] = null;
       ret = new int[str.length()/size];
       for (i=0; i<str.length(); i+=size){        
      	 if(i+size<str.length()){      		 
      		 ret[i/size] = ConvertHelper.toHex(str.substring(i,i+size));
      	 }else{
      		 ret[i/size] = ConvertHelper.toHex(str.substring(i));
      	 }
       }
       return ret;
   }
   /**
    * 
    * @param state
    * @return
    */
   private int[] subBytes(int state[]) {
       int S[] = Decrypt ? SBoxInv: SBox;
       int temp[] = new int[16];
       int i;
       for (i = 0; i < 16; i++) {
           temp[i] = S[state[i]];
       }
       return temp;
   }

   private int[] shiftRows (int state[]) {
       int temp[] = new int[16];
       int shiftBy[] =Decrypt?decryptShiftBy:ecryptShiftBy;
       int i;
       for (i = 0; i < 16; i++) {
           temp[i] = state[shiftBy[i]];
       }
       return temp;
   }

   private int[] mixColumns (int state[]) {
       int t[] = new int[16];
       int c;
       if (!Decrypt) {
           for (c = 0; c < 4; c++) {
               t[c * 4] = (int) (G2X[state[c * 4]] ^ G3X[state[1 + c * 4]] ^ state[2 + c * 4] ^ state[3 + c * 4]);
               t[1 + c * 4] = (int) (state[c * 4] ^ G2X[state[1 + c * 4]] ^ G3X[state[2 + c * 4]] ^ state[3 + c * 4]);
               t[2 + c * 4] = (int) (state[c * 4] ^ state[1 + c * 4] ^ G2X[state[2 + c * 4]] ^ G3X[state[3 + c * 4]]);
               t[3 + c * 4] = (int) (G3X[state[c * 4]] ^ state[1 + c * 4] ^ state[2 + c * 4] ^ G2X[state[3 + c * 4]]);
           }
       }else {
           for (c = 0; c < 4; c++) {
               t[c*4] = (int) (GEX[state[c*4]] ^ GBX[state[1+c*4]] ^ GDX[state[2+c*4]] ^ G9X[state[3+c*4]]);
               t[1+c*4] = (int) (G9X[state[c*4]] ^ GEX[state[1+c*4]] ^ GBX[state[2+c*4]] ^ GDX[state[3+c*4]]);
               t[2+c*4] = (int) (GDX[state[c*4]] ^ G9X[state[1+c*4]] ^ GEX[state[2+c*4]] ^ GBX[state[3+c*4]]);
               t[3+c*4] = (int) (GBX[state[c*4]] ^ GDX[state[1+c*4]] ^ G9X[state[2+c*4]] ^ GEX[state[3+c*4]]);
           }
       }
       
       return t;
   }
   private int[] invertArr (int arr[]) {
       int i;
       int ret[] = new int[arr.length];
       for (i=0; i<arr.length; i++){
           int j = arr[i]>=0?arr[i]:256+arr[i];
           if(j==256){
        	   System.out.println(arr[i]);
           }
           ret[j] = (int)i;
       }
       return ret;
   }
   private int Gxx(int a, int b) {
       int i;
       int ret;

       ret = 0;
       for (i=0; i<8; i++) {
           ret = (int) (((b&1)==1) ? ret^a : ret);
           /* xmult */
           a = (int) ((a>0x7f) ? 0x11b^(a<<1) : (a<<1));
           b >>>= 1;
       }

       return ret;
   }
   
   private int[] Gx(int x) {
       int i;
       int r[]= new int[256];
       for (i=0; i<256; i++){
           r[i] = Gxx(x, (int)i);
       }
       return r;
   }
   private int[] decryptBlock(int block[], int[][] words) {
       Decrypt = true;
       int[] state = addRoundKey(block, words, Nr);
       int round;
       for (round = Nr - 1; round > -1; round--) {
           state = shiftRows(state);
           state = subBytes(state);
           state = addRoundKey(state, words, round);
           if (round > 0) {
               state = mixColumns(state);
           }
           //last round? don't mixColumns
       }

       return state;
   }
   /**
    * 检查密钥长度是否设置
    * @throws Exception
    */
   private void checkSize() throws Exception{
	   if(this.size==0)
		   throw new Exception("尚未设置密钥长度!");
   }
   
}
